﻿namespace MonetDbDataProvider
{
    using System;
    using System.Data;
    using System.Text.RegularExpressions;

    public class MonetDbCommand : IDbCommand
    {
        public IDbConnection Connection { get; set; }

        public IDbTransaction Transaction { get; set; }

        public string CommandText { get; set; }

        public int CommandTimeout 
        { 
            get
            {
                return 0;
            }

            set    
            {
                throw new MonetDbProviderException("Query timeouts are not supported by MonetDb.");
            } 
        }

        public CommandType CommandType { get; set; }

        public IDataParameterCollection Parameters { get; private set; }

        public UpdateRowSource UpdatedRowSource { get; set; }

        public MonetDbCommand(IDbConnection connection)
        {
            this.Connection = connection;
            this.Parameters = new MonetDbParameterCollection();
        }

        public void Dispose()
        {
            this.Connection.Dispose();
        }

        public void Prepare()
        {
            throw new System.NotImplementedException();
        }

        public void Cancel()
        {
            throw new MonetDbProviderException("Query cancelling is not currently supported by MonetDB.");
        }

        public IDbDataParameter CreateParameter()
        {
            return new MonetDbParameter();
        }

        public int ExecuteNonQuery()
        {
            CheckConnectionStatus();

            return
                new MonetDbDataReader(
                    ((MonetDbConnection)this.Connection)
                    .MonetDbSocket.ExecuteCommand(this.ProcessCommandParameters(this.CommandText)), this)
                        .queryResults[0].TupleCount;
        }

        public IDataReader ExecuteReader()
        {
            CheckConnectionStatus();

            return
                new MonetDbDataReader(
                    ((MonetDbConnection)this.Connection)
                    .MonetDbSocket.ExecuteCommand(this.ProcessCommandParameters(this.CommandText)), this);
        }

        public IDataReader ExecuteReader(CommandBehavior behavior)
        {
            throw new System.NotImplementedException();
        }

        public object ExecuteScalar()
        {
            CheckConnectionStatus();

            using (IDataReader reader = new MonetDbDataReader(((MonetDbConnection)this.Connection)
                .MonetDbSocket.ExecuteCommand(this.ProcessCommandParameters(this.CommandText)), this))
                return (reader.Read()) ? reader[0] : null;
        }

        private static string EscapeParameterValue(string value)
        {
            return value.Replace(@"\", @"\\").Replace("'", "\\'");
        }

        private static string FormatParameterValue(IDataParameter parameter)
        {
            if (string.IsNullOrEmpty(Convert.ToString(parameter.Value))) 
                return "NULL";

            if (parameter.Value is DateTime)
                return ((DateTime)parameter.Value).ToString("yyyy-MM-dd HH:mm:ss");

            return Convert.ToString(parameter.Value);
        }

        private string ProcessCommandParameters(string commandText)
        {
            foreach (IDataParameter param in this.Parameters)
            {
                try
                {
                    commandText = Regex.Replace(
                                        commandText, param.ParameterName, string.Concat("'", EscapeParameterValue(FormatParameterValue(param)), "'"), RegexOptions.IgnoreCase);
                }
                catch (Exception ex)
                {
                    throw new MonetDbProviderException("Error encountered while applying Data Parameter.", ex);
                }
            }

            return commandText;
        }

        private void CheckConnectionStatus()
        {
            if(this.Connection.State != ConnectionState.Open)
                throw new MonetDbProviderException("MonetDb connection is not open.");
        }
    }
}
